2019-10-21 | UNLOCK

2019-9-21-c++ 多线程(c++11标准以前)

c++ 多线程(c++11标准以前)

基础

C++标准并没有提供对多进程并发的原生支持,所以C++的多进程并发要靠其他API——这需要依赖相关平台。

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。

POSIX:

可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称。

C++11 标准提供了一个新的线程库,内容包括了管理线程、保护共享数据、线程间的同步操作、低级原子操作等各种类。标准极大地提高了程序的可移植性,以前的多线程依赖于具体的平台,而现在有了统一的接口进行实现。

C++11 新标准中引入了几个头文件来支持多线程编程:

  • < thread > :包含std::thread类以及std::this_thread命名空间。
  • < atomic > :包含std::atomic和std::atomic_flag类,以及一套C风格的原子类型和与C兼容的原子操作的函数。
  • < mutex > :包含了与互斥量相关的类以及其他类型和函数。
  • < future > :包含两个Provider类(std::promise和std::package_task)和两个Future类(std::future和std::shared_future)以及相关的类型和函数。
  • < condition_variable > :包含与条件变量相关的类,包括std::condition_variable和std::condition_variable_any。

创建线程

1
2
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) ;

pthread_create的四个参数

  • thread:指向线程标识符指针。

  • attr:一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。

  • start_routine:线程运行函数起始地址,一旦线程被创建就会执行。

  • arg:运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。

终止线程

1
2
#include <pthread.h>
pthread_exit (status)

在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。线程在运行完成后会自动隐式地退出。

例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5
// 线程的运行函数
void *PrintHello(void *threadid)
{
// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
int tid = *((int*)threadid);
cout << "Hello Runoob! 线程 ID, " << tid << endl;
pthread_exit(NULL);
}

int main ()
{
// 定义线程的 id 变量,多个变量使用数组
pthread_t threads[NUM_THREADS];
int indexes[NUM_THREADS];// 用数组来保存i的值
int rc;
int i;
for( i=0; i < NUM_THREADS; i++ ){
cout << "main() : 创建线程, " << i << endl;
indexes[i] = i; //先保存i的值
//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
//线程函数参数传入的时候必须强制转换为void* 类型,即无类型指针
rc = pthread_create(&threads[i], NULL,
PrintHello, (void *)&(indexes[i]));
if (rc){
cout << "Error:无法创建线程," << rc << endl;
exit(-1);
}
}
//等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
pthread_exit(NULL);
}

编译并运行后预期产生如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
$ g++ test.cpp -lpthread -o test.o
$ ./test.o
main() : 创建线程, 0
main() : 创建线程, 1
Hello Runoob! 线程 ID, 0
main() : 创建线程, Hello Runoob! 线程 ID, 21

main() : 创建线程, 3
Hello Runoob! 线程 ID, 2
main() : 创建线程, 4
Hello Runoob! 线程 ID, 3
Hello Runoob! 线程 ID, 4

实际在Ubuntu16中测试结果却是:

1
2
3
4
5
6
7
8
9
10
main() : 创建线程, 0
main() : 创建线程, 1
main() : 创建线程, 2
main() : 创建线程, 3
main() : 创建线程, 4
Hello Runoob! 线程 ID, 4
Hello Runoob! 线程 ID, 3
Hello Runoob! 线程 ID, 2
Hello Runoob! 线程 ID, 1
Hello Runoob! 线程 ID, 0

非常规律,目前原因未知。

例子2(包含pthread_join的使用)

pthread_join()函数原型:

int pthread_join(pthread_t thread, void **retval);

两个参数:

  • thread: 线程标识符,即线程ID,标识唯一线程。
  • retval: 用户定义的指针,用来存储被等待线程的返回值。

返回值: 线程连接的状态,0是成功,非0是失败

linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。

unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>//包含unix系统中的各种原语函数,如sleep、read、write、fork,unistd.h在unix中类似于window中的windows.h

using namespace std;

#define NUM_THREADS 5

void *wait(void *t)
{
int i;
long tid;

tid = (long)t;

sleep(1);
cout << "Sleeping in thread " << endl;
cout << "Thread with id : " << tid << " ...exiting " << endl;
pthread_exit(NULL);
}

int main ()
{
int rc;
int i;
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;

// 初始化并设置线程为可连接的(joinable)
// 属性对象必须初始化,否则属性不能生效,创建线程时将返回错误。
// 函数将对象属性初始化为其缺省值,并分配一些存储空间,所以需要下面的函数删除初始化期间分配的存储空间。
// 参数:指向一个线程属性的指针。
pthread_attr_init(&attr);//没有这句会发生Segmentation fault (core dumped)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

for( i=0; i < NUM_THREADS; i++ ){
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
if (rc){
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}

// 删除属性,并等待其他线。
// 属性对象被销毁,并不影响线程的属性。
pthread_attr_destroy(&attr);
for( i=0; i < NUM_THREADS; i++ ){
rc = pthread_join(threads[i], &status);
if (rc){
cout << "Error:unable to join," << rc << endl;
exit(-1);
}
cout << "Main: completed thread id :" << i ;
cout << " exiting with status :" << status << endl;
}

cout << "Main: program exiting." << endl;
pthread_exit(NULL);
}

在ubuntu16编译后结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Thread with id : 140724715288192 ...exiting
Sleeping in thread
Thread with id : 140724715288192 ...exiting
Sleeping in thread
Thread with id : 140724715288192 ...exiting
Sleeping in thread
Thread with id : 140724715288192 ...exiting
Sleeping in thread
Thread with id : 140724715288192 ...exiting
Main: completed thread id :0 exiting with status :0
Main: completed thread id :1 exiting with status :0
Main: completed thread id :2 exiting with status :0
Main: completed thread id :3 exiting with status :0
Main: completed thread id :4 exiting with status :0
Main: program exiting.

这里有一点很困惑我,把tid=(long)t;改成tid=*((long*)t)后,每个线程的id显示都是0。

显示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Thread with id : 0 ...exiting
Sleeping in thread
Thread with id : 0 ...exiting
Sleeping in thread
Thread with id : 0 ...exiting
Sleeping in thread
Thread with id : 0 ...exiting
Sleeping in thread
Thread with id : 0 ...exiting
Main: completed thread id :0 exiting with status :0
Main: completed thread id :1 exiting with status :0
Main: completed thread id :2 exiting with status :0
Main: completed thread id :3 exiting with status :0
Main: completed thread id :4 exiting with status :0
Main: program exiting.

不论是储存线程创建时的值,还是公用同一片int空间,都不应该是0啊,如果是公用一个int的空间那在for循环结束后i的内存空间的值也应该是5。这里目前还弄不明白。

参考

https://www.cnblogs.com/shuqingstudy/p/9747004.html C++中的并发与多线程

https://www.runoob.com/cplusplus/cpp-multithreading.html 菜鸟课程C++多线程

https://baike.baidu.com/item/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3/12718298?fr=aladdin&fromtitle=POSIX&fromid=3792413 百度百科可移植操作系统接口

https://www.cnblogs.com/x_wukong/p/5671137.html linux的<pthread.h>

https://blog.csdn.net/liuzhanchen1987/article/details/8009701 linux标准库#include

https://www.cnblogs.com/leijiangtao/p/3995826.html 线程属性的初始化以及销毁

https://blog.csdn.net/weibo1230123/article/details/81410241 linux中pthread_join()与pthread_detach()详解

评论加载中